#!/usr/bin/env ruby

# ---------------------------------------------------------------------------- #
# Copyright 2002-2023, OpenNebula Project, OpenNebula Systems                  #
#                                                                              #
# Licensed under the Apache License, Version 2.0 (the "License"); you may      #
# not use this file except in compliance with the License. You may obtain      #
# a copy of the License at                                                     #
#                                                                              #
# http://www.apache.org/licenses/LICENSE-2.0                                   #
#                                                                              #
# Unless required by applicable law or agreed to in writing, software          #
# distributed under the License is distributed on an "AS IS" BASIS,            #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.     #
# See the License for the specific language governing permissions and          #
# limitations under the License.                                               #
# ---------------------------------------------------------------------------- #

ONE_LOCATION ||= ENV['ONE_LOCATION']

if !ONE_LOCATION
    RUBY_LIB_LOCATION ||= '/usr/lib/one/ruby'
    GEMS_LOCATION     ||= '/usr/share/one/gems'
else
    RUBY_LIB_LOCATION ||= ONE_LOCATION + '/lib/ruby'
    GEMS_LOCATION     ||= ONE_LOCATION + '/share/gems'
end

# %%RUBYGEMS_SETUP_BEGIN%%
if File.directory?(GEMS_LOCATION)
    real_gems_path = File.realpath(GEMS_LOCATION)
    if !defined?(Gem) || Gem.path != [real_gems_path]
        $LOAD_PATH.reject! {|l| l =~ /vendor_ruby/ }

        # Suppress warnings from Rubygems
        # https://github.com/OpenNebula/one/issues/5379
        begin
            verb = $VERBOSE
            $VERBOSE = nil
            require 'rubygems'
            Gem.use_paths(real_gems_path)
        ensure
            $VERBOSE = verb
        end
    end
end
# %%RUBYGEMS_SETUP_END%%

$LOAD_PATH << RUBY_LIB_LOCATION
$LOAD_PATH << File.dirname(__FILE__)

require 'vcenter_driver'
require 'open3'
require 'rexml/document'

def file_location?(target_path, f)
    target_path == File.basename(f)
end

def last_file_to_upload?(index, files_to_upload)
    index == files_to_upload.size - 1
end

def there_is_not_system_error?(rc)
    !rc
end

def find_image_type_in_directory(target_path)
    img_files=Dir.glob("#{target_path}/*")
    if img_files.empty?
        raise StandardError, 'No files in img_path folder'
    end

    file_types={}
    img_files.each do |f|
        Open3.popen3("qemu-img info #{f}") do |_sdtin, stdout, stderr, wait_thr|
            unless wait_thr.value.success?
                raise StandardError,
                      "'qemu-img info' failed, " \
                      "stderr: #{stderr.read}"
            end
            output = stdout.read
            tmp_file_type=output.split("\n")[1].split(':')[1].lstrip
            if tmp_file_type.nil?
                raise StandardError,
                      "'qemu-img info' output unexpected format"
            end
            file_types[f] = tmp_file_type
            wait_thr.join
        end
    end

    if file_types.values.include? 'vmdk'
        # image should already be in vmdk format
        return target_path, 'vmdk'
    end

    if file_types.empty?
        raise StandardError,
              "No valid images found in #{target_path}"
    end

    if file_types.length > 1
        raise StandardError,
              "More than 1 image file found in #{target_path}"
    end

    file_types[0]
end

def find_image_type(target_file)
    target_file_type=nil
    Open3.popen3("qemu-img info #{target_file}") do |_stdin, stdout, stderr, wait_thr|
        unless wait_thr.value.success?
            raise StandardError,
                  "'qemu-img info' failed, stderr: #{stderr.read}"
        end
        target_file_type=stdout.read.split("\n")[1].split(':')[1].lstrip
        if target_file_type.nil?
            raise StandardError,
                  "'qemu-img info' output unexpected format"
        end
        wait_thr.join
    end
    target_file_type
end

id             = ARGV[0]
drv_action_enc = STDIN.read

drv_action = OpenNebula::XMLElement.new
drv_action.initialize_xml(Base64.decode64(drv_action_enc),
                          'DS_DRIVER_ACTION_DATA')

DRV_ACTION_DS = '/DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/'

img_path     = drv_action['/DS_DRIVER_ACTION_DATA/IMAGE/PATH']
ds_id        = drv_action['/DS_DRIVER_ACTION_DATA/DATASTORE/ID']
ds_ref       = drv_action[DRV_ACTION_DS + 'VCENTER_DS_REF']
ds_image_dir = drv_action[DRV_ACTION_DS + 'VCENTER_DS_IMAGE_DIR']
               .match(%r{^/*(.*?)/*$})[1] rescue 'one'
md5          = drv_action['/DS_DRIVER_ACTION_DATA/IMAGE/TEMPLATE/MD5']
sha1         = drv_action['/DS_DRIVER_ACTION_DATA/IMAGE/TEMPLATE/SHA1']
nodecomp     = drv_action[DRV_ACTION_DS + 'NO_DECOMPRESS']
limit_bw     = drv_action[DRV_ACTION_DS + 'LIMIT_TRANSFER_BW']

check_valid img_path, 'img_path'

# if image is already in a vCenter datastore return the path
if img_path.start_with? 'vcenter://'
    img_path = img_path.sub('vcenter://', '')
    img_path = VCenterDriver::FileHelper.escape_path(img_path)
    puts "#{img_path.gsub("\n", '')} vmdk"
    exit(0)
end

temp_file = nil
filename = File.basename(img_path)
target_path = "#{ds_image_dir}/#{id}"

# If image is in a remote http location it has to be downloaded
# or if is a zipped file it has to be unzipped in a temp folder

if VCenterDriver::FileHelper.remote_or_needs_unpack?(img_path)
    temp_folder = File.join(VAR_LOCATION, "vcenter/#{target_path}")
    temp_file = File.join(temp_folder, File.basename(img_path))
    # if the original file doesnt have the vmdk extension, add it
    if !temp_file.match(/\.vmdk$/) &&
       !temp_file.match(/\.iso$/) &&
       !VCenterDriver::FileHelper.from_s3?(img_path)
        temp_file += '.vmdk'
    end

    # Create tmp directory
    FileUtils.mkdir_p(temp_folder)

    # Specify downloader args
    downsh_args = ' '
    downsh_args << "--md5 #{md5} " if md5 && !md5.empty? && !md5.eql?('-')
    downsh_args << "--sha1 #{sha1} " if sha1 && !sha1.empty?
    downsh_args << '--nodecomp ' if nodecomp && !nodecomp.empty?
    downsh_args << "--limit #{limit_bw} " if limit_bw && !limit_bw.empty?

    # Define downloader script and driver action variable
    downloader = "#{File.dirname(__FILE__)}/../downloader.sh #{downsh_args}"
    b64        = Base64.encode64(drv_action.to_xml).gsub!("\n", '')
    rc = system("DRV_ACTION=#{b64} #{downloader} #{img_path} #{temp_file}")

    # Fail if download fails
    if there_is_not_system_error?(rc)
        STDERR.puts "Error downloading #{img_path}"
        FileUtils.rm_rf(temp_file)
        exit(-1)
    end

    # Convert from current format to VMDK
    unless VCenterDriver::FileHelper.from_s3?(img_path) ||
           VCenterDriver::FileHelper.iso?(temp_file)
        file_type=nil
        temp_file_folder=nil
        begin
            if File.directory?(temp_file)
                temp_file_folder = temp_file
                temp_file, file_type = find_image_type_in_directory(temp_file)
            else
                file_type = find_image_type(temp_file)
            end
        rescue StandardError => e
            STDERR.puts "Failed to determine image type, error: #{e}"
            STDERR.puts "Removing #{temp_file}..."
            FileUtils.rm_rf(temp_file)
            unless temp_file_folder.nil?
                STDERR.puts "Removing #{temp_file_folder}."
                FileUtils.rm_rf(temp_file_folder)
            end

            exit(-1)
        end

        begin
            sparse_images = VCenterDriver::CONFIG[:sparse_images]
            out_dir  = File.dirname(temp_file)+'.tmp'
            out_file = File.basename(temp_file, '.*')+'.vmdk'
            out_path = out_dir + '/' + out_file
            Dir.mkdir(out_dir)
            if file_type != 'vmdk'
                convert_cmd = 'qemu-img convert ' \
                               "-f #{file_type} -O vmdk"
                if sparse_images
                    convert_cmd += ' -o subformat=monolithicSparse'
                else
                    convert_cmd += ' -o subformat=monolithicFlat'
                end

                convert_cmd += " #{temp_file} #{out_path}"
                convert = system(convert_cmd)
                if there_is_not_system_error?(convert)
                    STDERR.puts 'qemu-img convert failed.'
                    raise StandardError
                end

                FileUtils.rm_rf(temp_file)
                FileUtils.mv(out_dir, temp_file)
            elsif file_type == 'vmdk'
                # If it is vmdk, make sure it matches configuration
                if !temp_file_folder.nil?
                    info = VCenterDriver::FileHelper
                           .vcenter_file_info(temp_file_folder)
                else
                    info = VCenterDriver::FileHelper
                           .vcenter_file_info(temp_file)
                end

                # If the type matches, neither will trigger
                unless (info[:type] == :standalone && sparse_images) ||
                       (info[:type] == :flat && !sparse_images)
                    convert_cmd = 'qemu-img convert -f vmdk -O vmdk'
                    convert=true
                    if info[:type] == :standalone && !sparse_images
                        convert_cmd += ' -o subformat=monolithicFlat '\
                                       " #{temp_file} #{out_path}"
                        convert = system(convert_cmd)
                    elsif info[:type] == :flat && sparse_images
                        convert_cmd += ' -o subformat=monolithicSparse '\
                                       " #{temp_file} #{out_path}"
                        convert = system(convert_cmd)
                    end

                    if there_is_not_system_error(convert)
                        raise StandardError
                    end

                    # For the case the original vmdk was not in a folder
                    if !temp_file_folder.nil?
                        FileUtils.rm_rf(temp_file_folder)
                        FileUtils.mv(out_dir, temp_file_folder)
                    else
                        FileUtils.rm_rf(tmp_file)
                        FileUtils.mv(out_dir, temp_file)
                    end
                end
            end
        rescue StandardError
            STDERR.puts "Error converting #{temp_file}."
            STDERR.puts "Removing #{temp_file} and #{out_dir}."
            FileUtils.rm_rf(out_dir)
            FileUtils.rm_rf(temp_file)
            unless temp_file_folder.nil?
                STDERR.puts "Removing #{temp_file_folder}."
                FileUtils.rm_rf(temp_file_folder)
            end

            exit(-1)
        end
    end

    if !temp_file_folder.nil?
        img_path = temp_file_folder
    else
        img_path = temp_file
    end
end

# Time to upload files to vCenter
files_to_upload = []

info = VCenterDriver::FileHelper.vcenter_file_info(img_path)
extension = info[:extension] || ''

case info[:type]
when :standalone
    files_to_upload << info[:file]
when :flat
    files_to_upload = info[:flat_files]
    files_to_upload << info[:file]
end

files_to_upload.each_with_index do |f, index|
    path = "#{target_path}/#{File.basename(f)}"

    # Change path for gzipped standalone file
    if file_location?(target_path, f)
        path = "#{target_path}/#{filename}"

        # remove gz or bz2 if part of filename
        if path.end_with?('gz') && VCenterDriver::FileHelper.vmdk?(f)
            path.gsub!(/gz$/, '')
        end

        if path.end_with?('bz2') && VCenterDriver::FileHelper.vmdk?(f)
            path.gsub!(/bz2$/, '')
        end
    end

    # Change path if vmdk is part of filename but it's not the extension
    # rubocop:disable Style/DoubleNegation
    if !!/[^.]+vmdk$/.match(path) && VCenterDriver::FileHelper.vmdk?(f)
        path.gsub!(/vmdk$/, '')
        extension = '.vmdk'
    end
    # rubocop:enable Style/DoubleNegation

    # Add iso extension if file is an ISO file
    if VCenterDriver::FileHelper.iso?(f)
        path = "#{File.dirname(path)}/#{File.basename(path, '.*')}"
        extension = '.iso'
    end

    if last_file_to_upload?(index, files_to_upload)
        uploader_args = "#{ds_id} #{ds_ref} #{path}#{extension} #{f}"
    else
        uploader_args = "#{ds_id} #{ds_ref} #{path} #{f} &> /dev/null"
    end

    cmd = "#{File.dirname(__FILE__)}/../vcenter_uploader.rb #{uploader_args}"

    target, stderr, status = Open3.capture3(cmd)

    if !status.success?
        STDERR.puts "Cannot upload file #{f}: #{stderr}"
        FileUtils.rm_rf(temp_file) if temp_file
        exit(-1)
    end

    if last_file_to_upload?(index, files_to_upload)
        puts "#{target.gsub("\n", '')} vmdk"
    end
end

FileUtils.rm_rf(temp_file) if temp_file
